﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Tron.DataStructures;
using Tron.Players.Simple;
using Tron.Players.Strategic;

namespace Tron.Players.Search
{
    class IDSMiniMaxSearchPlayer : Player
    {
        #region Fields for situation when player is alone in connected component.
        StrategicPlayer strategicPlayer;
        StrategicPlayerHelper strategicPlayerHelper = new StrategicPlayerHelper();
        bool opponentInSeperateComponent = false;
        #endregion

        public static readonly int EMPTY_BLOCK = Constants.EMPTY_BLOCK;
        public static readonly int DEFAULT_TURN_TIME = 200;

        int opponentIndex;
        Vector2 bestMove;
        int depthLimit = 3;

        public override void GameOver(Percepts percepts, List<int> playerScores)
        {
            base.GameOver(percepts, playerScores);
            opponentInSeperateComponent = false;
        }

        StrategicPlayerHelperHeuristic strategicPlayerHelperHeuristic = new StrategicPlayerHelperHeuristic();

        public IDSMiniMaxSearchPlayer(int playerIndex)
            : this(playerIndex, DEFAULT_TURN_TIME)
        {
            strategicPlayer = new StrategicPlayer(playerIndex);
        }

        /// <summary>
        /// Constructs an iterative deepening alpha beta search player with the given player index and move time.
        /// </summary>
        /// <param name="moveTime">The time in milliseconds that the player is given to move</param>
        public IDSMiniMaxSearchPlayer(int playerIndex, int moveTime)
            : base(playerIndex) { }

        public override void Update(Percepts percepts, Actions actions)
        {
            if (percepts.PlayerStates.Count != 2)
                throw new NotImplementedException();

            int[,] board = percepts.Board.GetCopyOfBoardArray();

            opponentIndex = (PlayerIndex == 1) ? 0 : 1;

            Vector2 playerPosition = percepts.PlayerStates[PlayerIndex].Position;
            Vector2 opponentPosition = percepts.PlayerStates[opponentIndex].Position;

            #region Handle situation when player is alone in connected component.
            if (opponentInSeperateComponent || !percepts.PlayerStates[opponentIndex].Alive ||
                strategicPlayerHelper.IsOpponentInSeperateComponent(board, playerPosition, opponentPosition))
            {
                opponentInSeperateComponent = true;
                strategicPlayer.Update(percepts, actions);
                return;
            }
            #endregion

            actions.MoveDirection = minimaxSearch(board,playerPosition,opponentPosition);
        }

        Vector2 minimaxSearch(int[,] board, Vector2 playerPosition, Vector2 opponentPosition)
        {
            #region Null Checks (Commented)
            //if (board == null)       throw new NullReferenceException("percepts is null");
            //if (agentLoc == null)    throw new NullReferenceException("agentLoc is null");
            //if (opponentLoc == null) throw new NullReferenceException("opponentLoc is null");
            #endregion

            #region Console Output (Commented)
            //Console.WriteLine("MinimaxSearch:");
            #endregion Console Output (Commented)

            maxValue(board, 0, playerPosition, opponentPosition);

            #region Console Output (Commented)
            //Console.WriteLine("  agent location:    {0}", playerPosition.ToString());
            //Console.WriteLine("  opponent location: {0}", opponentPosition.ToString());
            //Console.WriteLine("  SELECTED MOVE: {0}\n\n", bestMoves[savedIndex]);
            #endregion

            return bestMove;
        }

        double maxValue(int[,] board, int depth, Vector2 playerPosition, Vector2 opponentPosition)
        {
            double largestMin = Double.NegativeInfinity;
            bool isTerminal = true;
            if (depth < depthLimit)
            {
                List<Vector2> possibleMoveDirections = SimplePlayerHelper.GetPossibleDirectionsFromPosition(board, playerPosition);
                if (possibleMoveDirections.Count > 0)
                {
                    isTerminal = false;
                    foreach (Vector2 possibleMoveDirection in possibleMoveDirections)
                    {
                        Vector2 possibleNextPosition = playerPosition + possibleMoveDirection;

                        board[(int)possibleNextPosition.X, (int)possibleNextPosition.Y] = PlayerIndex;

                        double min = minValue(board, depth, possibleNextPosition, opponentPosition);

                        board[(int)possibleNextPosition.X, (int)possibleNextPosition.Y] = EMPTY_BLOCK;

                        if (min > largestMin)
                        {
                            largestMin = min;
                            if (depth == 0)
                            {
                                bestMove = possibleMoveDirection;
                            }
                        }
                    }
                }
                else
                {
                    // MAX has no options.  The utility here depends on whether MIN has any options (since MIN must go next).
                    if (SimplePlayerHelper.GetPossibleDirectionsFromPosition(board, opponentPosition).Count == 0)
                    {
                        return 0;
                    }
                    else
                    {
                        return -1;
                    }
                }
            }

            if (isTerminal)
            {
                largestMin = GetUtilityOfPosition(board, playerPosition, opponentPosition, depth + 1, true);
            }

            return largestMin;
        }

        double minValue(int[,] board, int depth, Vector2 playerPosition, Vector2 opponentPosition)
        {
            double smallestMax = Double.PositiveInfinity;
            bool isTerminal = true;
            if (depth < depthLimit)
            {
                // Make sure to consider moving onto the other player if he is adjacent (since MIN moves second in the search).
                List<Vector2> possibleMoveDirections = SimplePlayerHelper.GetPossibleDirectionsFromPositionIncludingInputPositionEvenIfItIsAWall(
                    board, opponentPosition, playerPosition);
                if (possibleMoveDirections.Count > 0)
                {
                    isTerminal = false;
                    foreach (Vector2 possibleMoveDirection in possibleMoveDirections)
                    {
                        Vector2 possibleNextPosition = opponentPosition + possibleMoveDirection;

                        if (possibleNextPosition == playerPosition)
                        {
                            // MIN moved onto MAX.
                            return 0;
                        }

                        board[(int)possibleNextPosition.X, (int)possibleNextPosition.Y] = opponentIndex;

                        double max = maxValue(board, depth + 1, playerPosition, possibleNextPosition);

                        board[(int)possibleNextPosition.X, (int)possibleNextPosition.Y] = EMPTY_BLOCK;

                        if (max < smallestMax)
                        {
                            smallestMax = max;
                        }
                    }
                }
                else
                {
                    // MAX moved but MIN has no options.
                    return 1;
                }
            }

            if (isTerminal)
            {
                smallestMax = GetUtilityOfPosition(board, playerPosition, opponentPosition, depth, false);
            }

            return smallestMax;
        }

        /// <summary>
        /// Returns a value between -1 and 1 that estimates how good a state is for a player.
        /// -1 must be returned if the player has lost.
        /// 0 must be returned if the player has tied.
        /// 1 must be returned if the player has won.
        /// </summary>
        /// <param name="calledFromMax">Decides whether a player collision is a -1 (from min) or a 0 (from max)</param>
        /// <returns></returns>
        private double GetUtilityOfPosition(int[,] board, Vector2 playerPosition, Vector2 opponentPosition, int depth, bool calledFromMax)
        {
            return strategicPlayerHelperHeuristic.Evaluate(board, depth, playerPosition, opponentPosition, true);
        }
    }
}
